﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Threading;
using System.Drawing;
using System.IO;
using System.Net;
using System.Text.RegularExpressions;
using System.Windows.Forms;

namespace OmegleStormer
{
    class OmegleConvo
    {
        public delegate void AdPerformedHandler(OmegleConvo sender, bool successful);
        public event AdPerformedHandler AdPerformed;

        public delegate void AdPossiblyPerformedHandler(OmegleConvo sender, string extraInfo);
        public event AdPossiblyPerformedHandler AdPossiblyPerformed;

        public delegate void CaptchaPerformedHandler(OmegleConvo sender, bool successful);
        public event CaptchaPerformedHandler CaptchaPerformed;

        public delegate void DecaptchaStartHandler(OmegleConvo sender);
        public event DecaptchaStartHandler DecaptchaStart;

        public delegate void DecaptchaServiceErrorHandler(OmegleConvo sender);
        public event DecaptchaServiceErrorHandler DecaptchaServiceError;

        public delegate void ConvoStartFailedHandler(OmegleConvo sender, bool kill);
        public event ConvoStartFailedHandler ConvoStartFailed;

        public delegate void ConnectionErrorHandler(OmegleConvo sender);
        public event ConnectionErrorHandler ConnectionError;

        public delegate void ReplyTimeOutHandler(OmegleConvo sender);
        public event ReplyTimeOutHandler ReplyTimeOut;

        public delegate void TimeoutErrorHandler(OmegleConvo sender);
        public event TimeoutErrorHandler TimeoutError;

        public delegate void PartnerDisconnectedHandler(OmegleConvo sender, string message);
        public event PartnerDisconnectedHandler PartnerDisconnected;

        public delegate void InstantDisconnectedHandler(OmegleConvo sender, string message);
        public event InstantDisconnectedHandler InstantDisconnected;

        public Communications communication;
        Communications eventCommunications = new Communications();
        BackgroundWorker worker;
        BackgroundWorker eventListener;
        Captcha captchaService;

        string domain = "http://omegle.com";//"http://promenade.omegle.com/"; 
        string ID = "";
        string EventMessage = "blanked";
        string lastSentMessage = "";
        bool loopEventUpdate = false;

        public DateTime ConversationStartTime;

        List<OmegleConversationBuilder.ConversationPartControl> conversationParts;

        public bool IsBusy { get; set; }

        public OmegleConvo(Captcha captchaService)
        {
            this.captchaService = captchaService;
            communication = new Communications();
            IsBusy = false;
            worker = new BackgroundWorker();
            worker.DoWork += worker_DoWork;
            worker.RunWorkerCompleted += worker_RunWorkerCompleted;

            eventListener = new BackgroundWorker();
            eventListener.DoWork += eventListener_DoWork;
            eventListener.RunWorkerCompleted += eventListener_RunWorkerCompleted;
        }

        void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            IsBusy = false;
        }

        void eventListener_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (loopEventUpdate)
                eventListener.RunWorkerAsync();
        }

        void eventListener_DoWork(object sender, DoWorkEventArgs e)
        {
            var postData = new Communications.PostData();
            postData.AddValue("id", ID);
            EventMessage = eventCommunications.PerformPost(domain + "events", postData, referer: domain);
        }

        public void PerformAd(List<OmegleConversationBuilder.ConversationPartControl> conversationParts, Communications.ProxyInfo proxyInfo)//, CookieContainer cookieMonster)
        {
            communication.proxyInfo = proxyInfo;
            eventCommunications.proxyInfo = proxyInfo;
            this.conversationParts = conversationParts;
            IsBusy = true;
            worker.RunWorkerAsync();
        }

        string GetNextEventMessage()
        {
            EventMessage = "blanked";

            if (!eventListener.IsBusy) 
                eventListener.RunWorkerAsync();

            while (EventMessage == "blanked" || eventListener.IsBusy)
            {
                Thread.Sleep(10);
            }

            return EventMessage;
        }

        bool CheckForConversationalError(bool useOldMessage = false)
        {
            if (!useOldMessage) GetNextEventMessage();

            if (EventMessage != null && EventMessage.Contains("strangerDisconnected"))
            {
                loopEventUpdate = false;

                if (EventMessage.Contains("[\"connected\"]"))
                    InstantDisconnected(this, EventMessage + ((lastSentMessage != null) && (lastSentMessage != "") ? " - " + lastSentMessage : ""));
                else
                    PartnerDisconnected(this, EventMessage + ((lastSentMessage != null) && (lastSentMessage != "") ? " - " + lastSentMessage : ""));

                return true;
            }
            return false;
        }

        void worker_DoWork(object sender, DoWorkEventArgs e)
        {
            //try
            //{
                var startPage = communication.PerformGet(domain);
                var domainSearch = Regex.Match(startPage, "<frame src=\"([^<]+)\">").Groups[1].Value;

                if (domainSearch != "" && domainSearch != null)
                {
                    domain = domainSearch;
                }

                int captchaID = 0;
                ID = communication.PerformGet(domain + "start?rcs=1&spid=", referer: domain);

                if (ID == "timeout")
                {
                    TimeoutError(this);
                    goto FinishConvo;
                }
                if (ID == null || ID.Length != 8)
                {
                    ConvoStartFailed(this, ID != null && ID != "");
                    goto FinishConvo;
                }

                ID = ID.Replace("\"", "");
                Communications.PostData postData;

                postData = new Communications.PostData();
                postData.AddValue("id", ID);
                EventMessage = communication.PerformPost(domain + "events", postData, referer: domain);

                if (CheckForConversationalError(true)) 
                    goto FinishConvo;

                //if ((EventMessage == null || EventMessage == "") || (!EventMessage.Contains("[\"connected\"]") && !EventMessage.Contains("recaptchaRequired")))
                //{
                //    ConnectionError(this);
                //    goto FinishConvo;
                //}

                #region Decaptcha
                if (EventMessage != null && EventMessage.Contains("recaptchaRequired"))
                {
                    DecaptchaStart(this);
                    string challengeID = EventMessage.Split(',')[1].Replace("\"", "").Replace("]", "").Trim();
                    string imgID = communication.PerformGet("http://www.google.com/recaptcha/api/challenge?k=" + challengeID);
                    imgID = imgID.Split(':')[2].Split(',')[0].Replace("'", "").Trim();
                    WebResponse streamToClose;
                    var image = communication.PerformGetWithStream("http://www.google.com/recaptcha/api/image?c=" + imgID, out streamToClose);
                    byte[] imageData = communication.StreamToByteArray(image);
                    streamToClose.Close();

                    string result = captchaService.SolveCatpcha(imageData, out captchaID);

                    if (result == "")
                    {
                        DecaptchaServiceError(this);
                        goto FinishConvo;
                    }

                    postData = new Communications.PostData();
                    postData.AddValue("id", ID);
                    postData.AddValue("challenge", imgID);
                    postData.AddValue("response", result);
                    var response = communication.PerformPost(domain + "recaptcha", postData);
                    communication.PerformGet("http://www.google.com/recaptcha/api/challenge?k=" + challengeID + "&ajax=1&cachestop=0.15571934883912042");

                    postData = new Communications.PostData();
                    postData.AddValue("id", ID);
                    var captchaStatus = communication.PerformPost(domain + "events", postData, referer: domain);

                    if (captchaStatus== null || captchaStatus.Contains("recaptchaRejected"))
                    {
                        CaptchaPerformed(this, false);
                        captchaService.ReportCaptcha(captchaID);
                        goto FinishConvo;
                    }
                    else
                        CaptchaPerformed(this, true);
                }

                #endregion

                loopEventUpdate = true;
                eventListener.RunWorkerAsync();

                ConversationStartTime = DateTime.Now;

                bool conversationPartSuccessUnknown = false;
                bool winSeen = false;

                foreach (OmegleConversationBuilder.ConversationPartControl part in conversationParts)
                {
                    lastSentMessage = part.ConversationInfo.Message;
                    var refresh = "";
                    switch (part.ConversationInfo.Trigger)
                    {
                        case OmegleConversationBuilder.ConversationPartControl.Triggers.WaitForSeconds:
                            Thread.Sleep((int)part.ConversationInfo.TriggerData * 1000);
                            break;
                        case OmegleConversationBuilder.ConversationPartControl.Triggers.WaitForReply:
                            int waitedCount = 0;
                            while (true)
                            {
                                refresh = GetNextEventMessage();
                                if (refresh.Contains("gotMessage"))
                                    break;
                                else if (CheckForConversationalError(true))
                                    goto FinishConvo;
                                else
                                {
                                    if (waitedCount > 60)
                                    {
                                        ReplyTimeOut(this);
                                        goto FinishConvo;
                                    }
                                    Thread.Sleep(1800);
                                    waitedCount++;
                                }
                            }
                            break;
                        case OmegleConversationBuilder.ConversationPartControl.Triggers.WaitForSpecificReply:
                            break;
                    }

                    int typingWait = (part.ConversationInfo.GenerateFromWPM ?
                        (int)(((part.ConversationInfo.Message.Length / 5.2) / part.ConversationInfo.TypingSeconds) * 60)
                        : part.ConversationInfo.TypingSeconds);


                    postData = new Communications.PostData();
                    postData.AddValue("id", ID);
                TryTypingAgain:
                    refresh = communication.PerformPost(domain + "typing", postData, referer: domain);
                    if (CheckForConversationalError(true)) goto FinishConvo;
                    if (refresh == null)
                        //goto TryTypingAgain;
                    conversationPartSuccessUnknown = true;
                    else if (refresh == "win")
                        winSeen = true;
                    Thread.Sleep(typingWait * 1000);

                    postData = new Communications.PostData();
                    postData.AddValue("msg", part.ConversationInfo.Message);
                    postData.AddValue("id", ID);
                TryAgain:
                    refresh = communication.PerformPost(domain + "send", postData, referer: domain);
                    if (CheckForConversationalError(true)) goto FinishConvo;
                    if (refresh == null)
                        //goto TryAgain;
                    conversationPartSuccessUnknown = true;
                    else if (refresh == "win")
                        winSeen = true;
                }

                loopEventUpdate = false;

                Thread.Sleep(1300);
                postData = new Communications.PostData();
                postData.AddValue("id", ID);
                var end = communication.PerformPost(domain + "disconnect", postData);

                if (!winSeen && conversationPartSuccessUnknown)
                    AdPossiblyPerformed(this, "Conversation duration: " + DateTime.Now.Subtract(ConversationStartTime).ToString() + ". Last Received: " + EventMessage+ ". Last Sent: " + lastSentMessage);
                else
                    AdPerformed(this, true);

            //}
            //catch (Exception ex)
            //{
            //    var exception = ex.GetType().ToString();
            //    if (exception == "System.Net.WebException")
            //    {
            //        ProxyFailed(this, false);
            //    }
            //    else
            //    {
            //        AdPerformed(this, false);
            //    }
            //}

            FinishConvo:
            loopEventUpdate = false;
            while (eventListener.IsBusy)
            {
                Thread.Sleep(40);
            }
        }
    }
}
